package br.edu.ufcg.splab.techniques.reduction.requirement;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import br.edu.ufcg.splab.generator.TestCase;

public class RequirementTracer {
	
	private List<InterfaceRequirement> requirements;
	private Map<String, List<InterfaceRequirement>> reqCoverage;
	private Map<String, List<TestCase>> testCasesThatSatisfy;
	private String typeRequirement;
	
	public RequirementTracer() {
		this.requirements = new ArrayList<InterfaceRequirement>();
		this.reqCoverage = new HashMap<String, List<InterfaceRequirement>>();
		this.testCasesThatSatisfy = new HashMap<String, List<TestCase>>();
		this.typeRequirement = "";
	}
	
	/**
	 * Returns the requirement set satisfied by a test case.
	 * @param testCase The test case.
	 * @return The requirement set satisfied by a test case.
	 */
	public List<InterfaceRequirement> getRequirementsSatisfied(TestCase testCase) {
		return this.reqCoverage.get(testCase.getTestCase().toString());
	}
	
	/**
	 * Returns the requirement set satisfied by a test case from a requirement list.
	 * @param testCase The test case.
	 * @param requirements The requirement list.
	 * @return The requirement set satisfied by a test case from a requirement list.
	 */
	public List<InterfaceRequirement> getRequirementsSatisfied(TestCase testCase, List<InterfaceRequirement> requirements){ 
		List<InterfaceRequirement> result = new ArrayList<InterfaceRequirement>();
		for (InterfaceRequirement requirement : requirements) {
			if(isCoveredBy(requirement, testCase)){
		       result.add(requirement);
			}
		}
		return result;
	}
		
	/**
	 * Returns the list of requirements.
	 * @return The list of requirements.
	 */
	public List<InterfaceRequirement> getRequirements() {
		return this.requirements;
	}

	/**
	 * Adds a relationship between a test case and a requirement.
	 * @param testCase The test case.
	 * @param requirement The requirement.
	 */
	public void addTestCaseAndReq(TestCase testCase, InterfaceRequirement requirement) {
		String toString = testCase.getTestCase().toString();
		List<InterfaceRequirement> reqs = this.reqCoverage.get(toString);
		if(reqs == null) {
			reqs = new ArrayList<InterfaceRequirement>();
		}
		reqs.add(requirement);
		this.reqCoverage.put(toString, reqs);
		
		List<TestCase> tcs = this.testCasesThatSatisfy.get(requirement.getLabel());
		if(tcs == null){
			tcs = new ArrayList<TestCase>();
		}
		tcs.add(testCase);
		this.testCasesThatSatisfy.put(requirement.getLabel(), tcs);
	}
	
	/**
	 * Returns the requirement set satisfied by a test case set.
	 * @param testCases The test cases.
	 * @return The requirement set satisfied by a test case set.
	 */
	public List<InterfaceRequirement> getRequirementsSatisfied(List<TestCase> testCases) {
		List<InterfaceRequirement> list = new ArrayList<InterfaceRequirement>();
		for (TestCase tc : testCases) {
			List<InterfaceRequirement> temp = this.getRequirementsSatisfied(tc);
			for (InterfaceRequirement req : temp) {
				if (!list.contains(req)) {
					list.add(req);
				}
			}
		}
		return list;
	}

	/**
	 * Adds a new requirement.
	 * @param req The new requirement to be added.
	 */
	public void addRequirement(InterfaceRequirement req) {
		this.requirements.add(req);
	}
	
	/**
	 * Adds a new requirement.
	 * @param req The new requirement to be added.
	 */
	public void addAllRequirement(List<InterfaceRequirement> reqs) {
		this.requirements.addAll(reqs);
	}


	/**
	 * Get the test cases that satisfy this requirement.
	 * @return Returns the test cases that satisfy it.
	 */
	public List<TestCase> getTestCasesThatSatisfy(InterfaceRequirement req) {
		return this.testCasesThatSatisfy.get(req.getLabel());
	}

	/**
	 * Defines the test cases that satisfy this requirement.
	 * @param testCasesThatSatisfy The list of the test cases that satisfy this requirement to set.
	 */
	public void setTestCasesThatSatisfy(InterfaceRequirement req, List<TestCase> testCasesThatSatisfy) {
		this.testCasesThatSatisfy.put(req.getLabel(), testCasesThatSatisfy);
	}

	/**
	 * Insert a new test case that satisfy this requirement.
	 * @param The test case that satisfy this requirement.
	 */
	public void addTestCaseThatSatisfy(InterfaceRequirement req, TestCase testCase) {
		this.testCasesThatSatisfy.get(req.getLabel()).add(testCase);
	}
	
	/**
	 * Get the size of the requirement, that is the number of test cases that cover this requirement. 
	 * @return The number of test cases that cover this requirement.
	 */
	public int cardinality(InterfaceRequirement req){
		if(this.testCasesThatSatisfy.get(req.getLabel()) != null){
			return this.testCasesThatSatisfy.get(req.getLabel()).size();
		}else{
			return 0;
		}
	}
	
	/**
	 * Verify if the given test case cover this requirement.
	 * @param testCase The test case to be analyzed.
	 * @return A boolean value indicating if this requirement is covered by the given test case. 
	 */
	public boolean isCoveredBy(InterfaceRequirement req, TestCase testCase){
		if(this.testCasesThatSatisfy.get(req.getLabel()) != null){
			return this.testCasesThatSatisfy.get(req.getLabel()).contains(testCase);
		}else{
			return false;
		}
	}
	
	/**
	 * Returns the essential test cases.
	 * @return The essential test cases.
	 */
	public List<TestCase> getEssentialTestCases() {
		Set<TestCase> result = new HashSet<TestCase>();
		for (InterfaceRequirement requirement : requirements) {
			if(this.cardinality(requirement) == 1){
				result.addAll(this.getTestCasesThatSatisfy(requirement));
			}
		}
		return new ArrayList<TestCase>(result);
	}
	
	/**
	 * Returns the essential test cases from a requirement set.
	 * @param requirements The requirement set.
	 * @return The essential test cases.
	 */
	public List<TestCase> getEssentialTestCases(List<InterfaceRequirement> requirements) {
		Set<TestCase> result = new HashSet<TestCase>();
		for (InterfaceRequirement requirement : requirements) {
			if(this.cardinality(requirement) == 1){
				result.addAll(getTestCasesThatSatisfy(requirement));
			}
		}
		return new ArrayList<TestCase>(result);
	}
	
	public String getTypeRequirement() {
		return typeRequirement;
	}

	public void setTypeRequirement(String typeRequirement) {
		this.typeRequirement = typeRequirement;
	}
		
}
